' polygon2d - 2D Polygon type and related types/functions
' written by the Math Wizard
' March 2009

#include "matrix2d.bas"                 ' Matrix types and operations
#include "camera2d.bas"                 ' Camera type and operations

' Declare constants
Const EDGETOP = 0
Const EDGEBOTTOM = 1
Const EDGELEFT = 2
Const EDGERIGHT = 3

' Polygon-related types

Type Vertex
    As Double       lx, ly, lh          ' Local coordinates (starting)      
    As Double       wx, wy, wh          ' World coordinates
    As Integer      sx, sy              ' Screen coordinates
End Type

Type Segment
    As Integer      v0, v1              ' Segment drawn from v0 to v1
    As Integer      hue
End Type

Type Polygon
    As Integer      nv, ns              ' # of vertices and segments
    As Vertex Ptr   verts               ' array of vertices
    As Segment Ptr  segs                ' array of segments

    Declare Constructor( _              ' read from a file
        ByRef   fname   As String _
    )
    Declare Destructor()
    Declare Sub     Transform( _        ' Local -> World
        ByVal   mx      As Matrix2D _
    )
    Declare Sub     Project( _          ' World -> Screen
        ByVal   cam     As Camera _
    )
    Declare Sub     Render()            ' Draw to video
    Declare Property bound( _           ' Bounding box values
        ByVal   e       As Integer _
    ) As Double
End Type

Constructor Polygon( _
    ByRef   fname   As String _
)
' Loads a polygon object from a file.

Dim As Integer      f                   ' file handle for the file
Dim As String       id                  ' check if file contains a Polygon2D
Dim As Vertex Ptr   vt, v               ' temp. holders for vertex info.
Dim As Segment Ptr  st, s               ' temp. holders for segment info.
Dim As Integer      i                   ' counter variable

f = FreeFile
Open fname For Input As #f
If Err = 2 Then
    Print "[polygon2d] There is an error with accessing the file:"
    Print fname
    Print "Check that the file exists and that you have access to it."
    With This
        .nv = 0: .ns = 0: .verts = 0: .segs = 0
    End With
    Return
End If

' The first line of the file needs to be "poly2d"
Line Input #f, id
If id <> "poly2d" Then
    Print "[polygon2d] The file:"
    Print fname
    Print "is not a Polygon2D file."
    With This
        .nv = 0: .ns = 0: .verts = 0: .segs = 0
    End With
    Return
End If

' Read the rest of the file
Input #f, This.nv, This.ns              ' no. of vertices & segments
vt = CAllocate(This.nv, Len(Vertex))
st = CAllocate(This.ns, Len(Segment))

' Read the vertex data
v = vt
For i = 0 To (This.nv - 1)
    Input #f, v->lx, v->ly
    v += 1
Next i

' Read the segment data
s = st
For i = 0 To (This.ns - 1)
    Input #f, s->v0, s->v1
    s += 1
Next i

' Assign the data into the polygon itself
This.verts = vt
This.segs = st

End Constructor

Destructor Polygon()
' Invalidate the information in the polygon
' to free up memory.

With This
    Deallocate .verts
    Deallocate .segs
    verts = 0
    segs = 0
    nv = 0
    ns = 0
End With

End Destructor

Sub Polygon.Transform( _
   ByVal    mx  As Matrix2D _
)
' Set the world coordinates of the polygon's vertices
' based on the transformation matrix specified.

Dim As Integer      i                   ' counter variable
Dim As Vertex Ptr   v = This.verts


' "Optimized" version - skips some calculations by
' assuming that the point vector is in homogeneous coords (h = 1)
' and that the matrix is a rotate, translate, or scale transformation
For i = 0 To (This.nv - 1)
    v->wx = (mx.a * v->lx) + (mx.b * v->ly) + mx.c  ' assume lh always = 1
    v->wy = (mx.d * v->lx) + (mx.e * v->ly) + mx.f
    v->wh = mx.i                                    ' assume g & h = 0
    v += 1
Next i

' "Correct" version included for comparison.
' To use, comment out the For block above and uncomment the
' For block below.
' For i = 0 To (This.nv - 1)
'     v->wx = (mx.a * v->lx) + (mx.b * v->ly) + (mx.c * v->lh)
'     v->wy = (mx.d * v->lx) + (mx.e * v->ly) + (mx.f * v->lh)
'     v->wh = (mx.g * v->lx) + (mx.h * v->ly) + (mx.i * v->lh)
'     v += 1
' Next i

End Sub

Sub Polygon.Project( _
    ByVal   cam As Camera _
)

' Set the screen coordinates of the polygon's vertices
' based on the information provided in the viewport.

Dim As Integer      i                   ' counter variable
Dim As Integer      wid, ht             ' info on screen
Dim As Vertex Ptr   v = This.verts

ScreenInfo wid, ht

' 2nd line, 1st term (wy - t) is negative
' because video memory has lower pixels addressed to
' higher y values (it is "left-handed").
For i = 0 To (This.nv - 1)
    v->sx = Fix((v->wx - cam.l) / (cam.r - cam.l) * wid)
    v->sy = ht - Fix((cam.b - v->wy) / (cam.b - cam.t) * ht)
    v += 1
Next i

End Sub

Sub Polygon.Render()

' Draws the polygon's segments to the screen. It is up to the 
' function user to verify that the screen dimensions are the 
' same as the ones specified in the Viewport that was called 
' most recently.

Dim As Integer      i                   ' counter variable
Dim As Segment Ptr  s = This.segs

For i = 0 To (This.ns - 1)
    ' Draw white for now
    Line (This.verts[s->v0].sx, This.verts[s->v0].sy) _
        - (This.verts[s->v1].sx, This.verts[s->v1].sy), _
        15
    s += 1
Next i

End Sub

Property Polygon.bound( _
    ByVal   e   As Integer _
) As Double
' Boundary accessor method.
' Returns the coordinate of the edge of the bounding box
' specified by the parameter e, which is an edge constant
' (see above). The polygon must have been already transformed
' (which would give it world coordinates).
' Basically, loops through all the vertices to see which one
' has the (least|most)(x|y-coordinate).

Dim As Double       record              ' record value read
Dim As Integer      i                   ' counter variable
Dim As Vertex Ptr   v = This.verts

' start with the first vertex
If e = EDGETOP Or e = EDGEBOTTOM Then
    record = v->wy
ElseIf e = EDGELEFT Or e = EDGERIGHT Then
    record = v->wx
Else
    Print "Polygon:bound: No such edge!"
    Exit Property
End If
v += 1                                  ' advance pointer counter

' now start comparing and keeping the record
For i = 1 To (This.nv - 1)
    Select Case e
        Case EDGETOP:       If v->wy > record Then record = v->wy
        Case EDGEBOTTOM:    If v->wy < record Then record = v->wy
        Case EDGELEFT:      If v->wx < record Then record = v->wx
        Case EDGERIGHT:     If v->wx > record Then record = v->wx
    End Select
    v += 1
Next i

Return record

End Property
